#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <mpi.h>
#include "utils.h"

/* Convention Definitions */
#define NUMBER_OF_ELEMENTS NUMBER_OF_SERVERS*NUMBER_OF_SERVERS
#define SERVER_DISTANCE_PATH "./initial.data/server.distance"
#define ORIGIN_SERVER_PATH "./initial.data/origin.server"


int insert_first_server(server_list *list, int server) {
  int size = -1;
  server_node *head = (server_node*) malloc(sizeof(server_node));

  if (head != NULL) {
    head->server = server;
    head->next = NULL;

    list->head = head;

    size = list->size = 1;
  }

  return size;
}

int find_server(server_list list, int server) {
  server_node *current = list.head;

  int found = 0;

  while (current != NULL) {
    if (current->server == server)
      found++;

    current = current->next;
  }

  return found;
}

int insert_server(server_list *list, int server) {
  int size = -1;
  int exists = find_server(*list, server);

  if (exists == 0) {
    server_node *node = list->head;
    server_node *new_node = (server_node*) malloc(sizeof(server_node));

    if (new_node != NULL) {
      new_node->server = server;
      new_node->next = NULL;

      while (node->next != NULL)
        node = node->next;

      node->next = new_node;

      size = list->size++;
    }
  }

  return size;

}

int delete_server(server_list *list, int server) {
  int size = -1;
  server_node *before = list->head;

  if (before != NULL) {
    while ((before->next != NULL) && (before->next->server != server)) {
      before = before->next;
    }

    if (before->next != NULL) {
      server_node *buffer = before->next;
      before->next = buffer->next;
      free(buffer);

      size = list->size--;
    }
  }

  return size;
}

int is_content_present(server_list list, int server) {
  int present = 0;

  if (list.head != NULL) {
    server_node *current = list.head;

    while ((current->next != NULL) && (current->server != server))
      current = current->next;

    if (current->server == server)
      present = 1;
  }

  return present;
}

int get_closest_server(server_list list, int distances[NUMBER_OF_SERVERS],int server) {
  int closest = -1;

  if (list.head != NULL) {
    closest = list.head->server;
    server_node *current = list.head->next;

    while (current != NULL) {
      if ((distances[current->server] < distances[closest]) && (current->server != server))
        closest = current->server;
      
      current = current->next;
    }
  }

  return closest;
}

void init_server_distance(int (*matrix)[NUMBER_OF_SERVERS]) {
  /* Open the file with the matrix of distances between servers */
  FILE *file;
  file = fopen(SERVER_DISTANCE_PATH, "r");

  if (file == NULL) {
    fprintf(stderr, "Can't open the server distance file: %s\n", SERVER_DISTANCE_PATH);
    MPI_Abort(MPI_COMM_WORLD, -1);
  }

  char str_value[255];
  int read_elements = 0;
  int line = 0, column = 0;

  /* Iterating through the server distance file reading items*/
  while (fscanf(file, "%s", str_value) != EOF) {
    line = read_elements / NUMBER_OF_SERVERS;
    column = read_elements % NUMBER_OF_SERVERS;

    matrix[line][column] = atoi(str_value);

    read_elements++;

    /* Check if the file has provided the right number of distances */
    if (read_elements > NUMBER_OF_ELEMENTS) {
      fprintf(stderr, "The server distance file - '%s' - must have a %d x %d matrix !\n",
              SERVER_DISTANCE_PATH, NUMBER_OF_SERVERS, NUMBER_OF_SERVERS);
      MPI_Abort(MPI_COMM_WORLD, -1);
    }
  }

  fclose(file);
}

void init_origin_server(server_list contents[NUMBER_OF_CONTENTS]) {
  /* Open the file with the vector of origin servers for each content */
  FILE *file;
  file = fopen(ORIGIN_SERVER_PATH, "r");

  if (file == NULL) {
    fprintf(stderr, "Can't open the origin server file: %s\n", ORIGIN_SERVER_PATH);
    MPI_Abort(MPI_COMM_WORLD, -1);
  }

  char str_value[255];
  int element = 0;

  /* Iterating through the server distance file reading items*/
  while (fscanf(file, "%s", str_value) != EOF) {
    int server = atoi(str_value);
    
    if ((server < 0) || (server >= NUMBER_OF_SERVERS)) {
      fprintf(stderr, "The elements in origin server file - '%s' - must be a natural number in [0, %d)!\n",
              ORIGIN_SERVER_PATH, NUMBER_OF_SERVERS);
      MPI_Abort(MPI_COMM_WORLD, -1);      
    }

    insert_first_server(&(contents[element]), server);

    element++;

    /* Check if the file has provided the right number of origins */
    if (element > NUMBER_OF_CONTENTS) {
      fprintf(stderr, "The origin server file - '%s' - must have %d elements!\n", ORIGIN_SERVER_PATH, NUMBER_OF_CONTENTS);
      MPI_Abort(MPI_COMM_WORLD, -1);      
    }

  }

  fclose(file);
}

void init_closest_replica(int replicas[NUMBER_OF_CONTENTS], server_list contents[NUMBER_OF_CONTENTS]) {
  int i;

  for (i = 0; i < NUMBER_OF_CONTENTS; i++) {
    replicas[i] = contents[i].head->server;
  }
}

matrix_position get_max_load(int matrix[NUMBER_OF_SERVERS][NUMBER_OF_CONTENTS]) {
  matrix_position max_pos;
  max_pos.line = 0;
  max_pos.column = 0;
  int i, j;

  for (i = 0; i < NUMBER_OF_SERVERS; i++) {
    for (j = 0; j < NUMBER_OF_CONTENTS; j++) {
      if (matrix[i][j] > matrix[max_pos.line][max_pos.column]) {
          max_pos.line = i;
          max_pos.column = j;
      }
    }
  }

  return max_pos;
}
